package com.wulis.provider.authority.service.impl;

import static com.wulis.common.constant.GlobalConstant.*;
import static com.wulis.common.model.enums.HttpStatusEnum.ACCOUNT_EXISTS;

import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wulis.common.exception.BusinessException;
import com.wulis.common.model.enums.DeletedStateEnum;
import com.wulis.common.model.enums.EnableStateEnum;
import com.wulis.common.support.BaseServiceImpl;
import com.wulis.common.utils.AuthUserUtil;
import com.wulis.common.utils.JwtTokenUtil;
import com.wulis.common.wrapper.ApiWrapMapper;
import com.wulis.common.wrapper.ApiWrapper;
import com.wulis.config.security.AuthUserDetails;
import com.wulis.provider.authority.mapper.AccountMapper;
import com.wulis.provider.authority.model.bo.UpdateAccountBo;
import com.wulis.provider.authority.model.dto.CodeLoginDto;
import com.wulis.provider.authority.model.dto.FormLoginDto;
import com.wulis.provider.authority.model.dto.SendCodeDto;
import com.wulis.provider.authority.model.entity.Account;
import com.wulis.provider.authority.service.AccountService;

import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * 权限管理:账号表 Service实现类
 *
 * @author WuliBao
 * @date 2020-04-17
 */
@Slf4j
@Service
@AllArgsConstructor
public class AccountServiceImpl extends BaseServiceImpl<AccountMapper, Account> implements AccountService {
    
    private final AccountMapper accountMapper;
    
    @Resource
    private final RedisTemplate<String, String> redisTemplate;
    
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    
    /**
     * WEB:插入账号
     *
     * @param account account
     * @param password password
     * @param enable enable
     * @return Long
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public long insertAccount(@NonNull String account, @NonNull String password, @NonNull Integer enable) {
        try {
            Account accountModel =
                new Account().setAccount(account).setPassword(bCryptPasswordEncoder.encode(password)).setEnable(enable);
            accountMapper.insert(accountModel);
            return accountModel.getId();
        }
        catch (DuplicateKeyException e) {
            // 此时账号已经存在,加入同步锁，判断该账号是否是被删除的账号，如果被删除账号则选择恢复该账号
            synchronized (this) {
                QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(Account::getAccount, account);
                Account accountModel = accountMapper.selectOne(queryWrapper);
                // 判断已存在账号是否为删除状态
                if (accountModel != null && DeletedStateEnum.IS_DELETED.value().equals(accountModel.getDeleted())) {
                    accountModel.setPassword(bCryptPasswordEncoder.encode(password))
                        .setEnable(enable)
                        .setDeleted(DeletedStateEnum.NOT_DELETED.value());
                    accountMapper.updateById(accountModel);
                    return accountModel.getId();
                }
            }
            throw new BusinessException(ACCOUNT_EXISTS);
        }
    }
    
    /**
     * WEB:修改账号
     *
     * @param updateAccountBo updateAccountBo
     * @return int
     */
    @Override
    public int updateAccount(@NonNull UpdateAccountBo updateAccountBo) {
        Account account = new Account();
        BeanUtils.copyProperties(updateAccountBo, account);
        if (account.getPassword() != null) {
            account.setPassword(bCryptPasswordEncoder.encode(account.getPassword()));
        }
        try {
            return accountMapper.updateById(account);
        }
        catch (DuplicateKeyException e) {
            throw new BusinessException(ACCOUNT_EXISTS);
        }
    }
    
    /**
     * WEB:删除账号
     *
     * @param accountId accountId
     * @return int
     */
    @Override
    public int deleteAccount(@NonNull Long accountId) {
        Account account = new Account();
        account.setId(accountId);
        account.setDeleted(DeletedStateEnum.IS_DELETED.value());
        return accountMapper.updateById(account);
    }
    
    /**
     * WEB:表单登录
     * 
     * @param formLoginDto formLoginDto
     * @return ApiWrapper
     */
    @Override
    public ApiWrapper<Object> formLogin(@NonNull FormLoginDto formLoginDto) {
        // 查询账号信息
        AuthUserDetails authUser = accountMapper.queryAuthUserByAccount(formLoginDto.getAccount());
        if (authUser == null) {
            return ApiWrapMapper.error("账号不存在");
        }
        // 校验密码
        if (!bCryptPasswordEncoder.matches(formLoginDto.getPassword(), authUser.getPassword())) {
            return ApiWrapMapper.error("密码错误");
        }
        // 判断账号是否被禁用
        if (EnableStateEnum.NOT_ENABLED.value().equals(authUser.getEnable())) {
            return ApiWrapMapper.error("账号被禁用");
        }
        // 生成token并保存登录状态
        return ApiWrapMapper.success(saveLoginStatus(authUser));
    }
    
    /**
     * WEB:验证码登录
     * 
     * @param codeLoginDto codeLoginDto
     * @return ApiWrapper
     */
    @Override
    public ApiWrapper<Object> codeLogin(@NonNull CodeLoginDto codeLoginDto) {
        // 校验短信验证码是否正确
        String code = redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + codeLoginDto.getPhoneNumber());
        if (code == null) {
            return ApiWrapMapper.error("验证码已失效");
        }
        if (Integer.valueOf(code).equals(codeLoginDto.getCode())) {
            // 查询账号信息
            AuthUserDetails authUser = accountMapper.queryAuthUserByPhoneNumber(codeLoginDto.getPhoneNumber());
            if (authUser == null) {
                return ApiWrapMapper.error("账号不存在");
            }
            // 判断账号是否被禁用
            if (EnableStateEnum.NOT_ENABLED.value().equals(authUser.getEnable())) {
                return ApiWrapMapper.error("账号被禁用");
            }
            // 生成token并保存登录状态
            return ApiWrapMapper.success(saveLoginStatus(authUser));
        }
        else {
            return ApiWrapMapper.error("验证码错误");
        }
    }
    
    /**
     * WEB:发送证码
     *
     * @param sendCodeDto sendCodeDto
     * @return ApiWrapper
     */
    @Override
    public ApiWrapper<Object> sendCode(@NonNull SendCodeDto sendCodeDto) {
        // 查询账号信息
        AuthUserDetails authUser = accountMapper.queryAuthUserByPhoneNumber(sendCodeDto.getPhoneNumber());
        if (authUser == null) {
            return ApiWrapMapper.error("账号不存在");
        }
        // 判断账号是否被禁用
        if (EnableStateEnum.NOT_ENABLED.value().equals(authUser.getEnable())) {
            return ApiWrapMapper.error("账号被禁用");
        }
        // TODO 调用短信服务发送短信
        String code = RandomStringUtils.random(6, false, true);
        // Redis中存入验证码,失效时间为1分钟
        redisTemplate.opsForValue()
            .set(VERIFICATION_CODE_PREFIX + sendCodeDto.getPhoneNumber(), code, 60, TimeUnit.SECONDS);
        return ApiWrapMapper.success();
    }
    
    /**
     * WEB:注销登录
     * 
     * @return ApiWrapper
     */
    @Override
    public ApiWrapper<Object> logout() {
        AuthUserDetails authUser = AuthUserUtil.getDetails();
        // 删除登录状态
        redisTemplate.delete(TOKEN_PREFIX + authUser.getAccountId());
        return ApiWrapMapper.success();
    }
    
    /**
     * 生成token 并记录登录状态
     * 
     * @param authUser authUser
     * @return token
     */
    private String saveLoginStatus(@NonNull AuthUserDetails authUser) {
        // 生成token
        String token = JwtTokenUtil.generateToken(authUser);
        // 存入redis,过期时间为TOKEN_EXPIRATION配置
        redisTemplate.opsForValue()
            .set(TOKEN_PREFIX + authUser.getAccountId(), token, TOKEN_EXPIRATION, TimeUnit.HOURS);
        return token;
    }
}
